
/**
 * Implemented by client
 */
export public interface ~ApiExecuterI {
    public <R, E> async exec(
        method: string,
        path: string,
        pathParams: ~Object+,
        queryParams: ~Object+,
        headerParams: ~Object+,
        payloadContentType: string,
        body: any+) : Promise<R, Object|ApiError<E>>;
}

export public interface ~ApiError<T> {
    public resultBody?: T;
}

{{#checkRequiredParamsNotNull}}
export public function checkRequiredParams(apiName: string, params: ~Object+) : void {
    for (const key of Object.keys(params)) {
        const arg = params[key];
        if (arg == null) {
            throw new Error('Required parameter ' + key + ' was null or undefined when calling ' + apiName + '.');
        }
    }
}
{{/checkRequiredParamsNotNull}}

{{#checkSuperfluousBodyProps}}
export public function <T> cleanCopyBody(t : T+, ...properties: string) : ~T {
    const copy : ~T+ = {};
    for (const prop in properties) {
        copy[prop] = t.prop;
    }
    return copy;
}
{{/checkSuperfluousBodyProps}}

{{#generateDefaultApiExecuter}}
/**
 * Default implementation of ApiExecuterI
 *
 * The following dependencies are necessary:
 * - n4js-runtime-esnext
 * - n4js-runtime-es2015
 * - n4js-runtime-html5
 */
export public class FetchApiExec implements ApiExecuterI {
    public apiOrigin: string;
    const jsonTypes = ["application/json", "application/problem+json"];

    @Override
    public <R, E> async exec(
        method: string,
        path: string,
        pathParams: ~Object+,
        queryParams: ~Object+,
        headerParams: ~Object+,
        payloadContentType: string,
        body: any+
    ): Promise<R, Object|ApiError<E>> {

        if (pathParams) {
            for (const [k, v] of Object.entries(pathParams)) {
                path = path.replace(`{${k}}`, encodeURIComponent(String(v)));
            }
        }
        const query: string[] = [];
        if (queryParams) {
            for (const [k, v] of Object.entries(queryParams)) {
                query.push(`${k}=${encodeURIComponent(String(v))}`);
            }
        }

        let url = `${this.apiOrigin}${path}`;
        if (query.length) {
            url += `?${query.join("&")}`;
        }

        const headers: Object+ = {};
        if (payloadContentType) {
            headers["content-type"] = payloadContentType;
            if (this.constructor.jsonTypes.includes(payloadContentType)) {
                body = JSON.stringify(body);
            }
        }
        Object.assign(headers, headerParams);

        return await this.<R,E>fetchExec(url, {
            method,
            headers,
            body,
        });
    }

    protected <R, E> async fetchExec(url: string, reqInit: RequestInit): Promise<R, Object|ApiError<E>> {
        const resp = await fetch(url, reqInit);

        if (resp.status !== 204) {
            const contentType = (resp.headers.get("content-type") || "").split(";")[0];
            const body = this.constructor.jsonTypes.includes(contentType)
                ? await resp.json()
                : await resp.text();

            if (!resp.ok) {
                await this.handleError(resp, body);
            }
            return body as R;
        }
        return null;
    }

    protected <E> async handleError(resp: Response, body): Promise<undefined, ApiError<E>> {
        throw {body: body};
    }
}
{{/generateDefaultApiExecuter}}
